using System;
using System.Collections.Generic;
using System.Linq;

using TrueCode.Core.Aspects;


namespace TrueCode.DependencyManager
{
  /// <summary>
  /// Represents a session of building of an object.
  /// </summary>
  public class BuildSession : IBuildSession
  {
    #region Fields
    private readonly IEnumerable< object > _buildStepsSequence;
    private readonly IEnumerator< object > _buildStepsEnumerator;

    private readonly BuildActionCollection _buildActions;
    private readonly BuildActionCollection _sessionalBuildActions;

    private readonly ServiceCollection _services;
    private readonly ServiceCollection _sessionalServices;
    private readonly Container _parentContainer;
    #endregion

    #region .ctors
    public BuildSession( [NotNull] SessionAim sessionAim,
                         [NotNull] IEnumerable< object > buildStepsSequence,
                         [NotNull] BuildActionCollection buildActions,
                         [CanBeNull] BuildActionCollection sessionalBuildActions,
                         [NotNull] ServiceCollection services,
                         [CanBeNull] ServiceCollection sessionalServices,
                         [CanBeNull] Container parentContainer )
    {
      #region Validation
      if( sessionAim == null ) throw new ArgumentNullException( "sessionAim" );
      if( buildStepsSequence == null || !buildStepsSequence.Any() )
        throw new ArgumentNullException( "buildStepsSequence" );
      if( buildActions == null ) throw new ArgumentNullException( "buildActions" );
      if( services == null ) throw new ArgumentNullException( "services" );
      #endregion

      SessionAim = sessionAim;
      _buildStepsSequence = buildStepsSequence;
      _buildStepsEnumerator = _buildStepsSequence.GetEnumerator();
      _buildActions = buildActions;
      _sessionalBuildActions = sessionalBuildActions;
      _services = services;
      _sessionalServices = sessionalServices;
      _parentContainer = parentContainer;
    }
    #endregion

    #region Properties
    /// <summary>
    /// Gets or sets aim of the session.
    /// </summary>
    public SessionAim SessionAim { get; set; }

    /// <summary>
    /// Gets or sets the instance of the building type.
    /// </summary>
    public object Instance { get; set; }
    #endregion

    #region Public
    /// <summary>
    /// Builds the object. Building process can do not include instantiating of the object if an instance is passed
    /// into <see cref="IBuildSession"> constructor. In that case building means applying build actions to the instance.</see>
    /// </summary>
    /// <returns>Returns built object.</returns>
    public object Build()
    {
      ExecuteNextBuildAction();

      if( Instance == null && _parentContainer != null )
        Instance = _parentContainer.BuildUp( SessionAim.Type, null, SessionAim.Setting, _sessionalBuildActions,
                                             _sessionalServices );
      return Instance;
    }

    /// <summary>
    /// Makes a copy of the session for a new aim.
    /// </summary>
    /// <param name="aimType"></param>
    /// <returns>Returns a new instance of the <see cref="IBuildSession"/> class.</returns>
    public IBuildSession MakeCopy( [NotNull] Type aimType )
    {
      return MakeCopy( aimType, null );
    }

    /// <summary>
    /// Makes a copy of the session for a new aim.
    /// </summary>
    /// <param name="aimType"></param>
    /// <param name="settingKey"></param>
    /// <returns>Returns a new instance of the <see cref="IBuildSession"/> class.</returns>
    public IBuildSession MakeCopy( [NotNull] Type aimType, object settingKey )
    {
      if( aimType == null ) throw new ArgumentNullException( "aimType" );
      return new BuildSession( new SessionAim( aimType, settingKey ), _buildStepsSequence, _buildActions,
                               _sessionalBuildActions, _services, _sessionalServices, _parentContainer );
    }


    /// <summary>
    /// Executes next build action from the actions chain.
    /// </summary>
    public void ExecuteNextBuildAction()
    {
      while( _buildStepsEnumerator.MoveNext() )
      {
        var buildAction = GetBuildAction( _buildStepsEnumerator.Current );
        if( buildAction != null )
        {
          buildAction.Execute( this );
          break;
        }
      }
    }

    /// <summary>
    /// Gets a service of type <typeparamref name="T"/>.
    /// </summary>
    /// <typeparam name="T">Type of a service.</typeparam>
    /// <returns>Returns a service of type <typeparamref name="T"/> or null if there is no service of 
    /// such type registered.</returns>
    public T GetService< T >() where T : class
    {
      return GetService< T >( null );
    }

    /// <summary>
    /// Gets a service of type <typeparamref name="T"/>.
    /// </summary>
    /// <typeparam name="T">Type of a service.</typeparam>
    /// <param name="key">Key with which a service is associated.</param>
    /// <returns>Returns a service of type <typeparamref name="T"/> or null if there is no service of 
    /// such type registered.</returns>
    public T GetService< T >( object key ) where T : class
    {
      return _sessionalServices != null
               ? ( _sessionalServices.GetNoDefault< T >( key, SessionAim )
                   // first try get a service from the sessional but no default
                   ??
                   _services.GetNoDefault< T >( key, SessionAim )
                   // if there is no, get from main storage but no default
                   ?? _sessionalServices.Get< T >( key, SessionAim ) // then get from sessional with default
                   ?? _services.Get< T >( key, SessionAim ) )
               : _services.Get< T >( key, SessionAim );
    }

    public T GetDefaultService< T >( object key = null ) where T : class
    {
      return _sessionalServices == null
               ? _services.Get< T >()
               : ( _sessionalServices.Get< T >() ?? _services.Get< T >() );
    }
    #endregion

    #region Private
    private IBuildAction GetBuildAction( object buildStep )
    {
      return _sessionalBuildActions != null
               ? ( _sessionalBuildActions.GetNoDefault( buildStep, SessionAim )
                   // first try get an action from the sessional but no default
                   ?? _buildActions.GetNoDefault( buildStep, SessionAim ) )
                 // if there is no, get from main storage but no default
                 ?? _sessionalBuildActions.Get( buildStep, SessionAim ) // then get from sessional with default
                 ?? _buildActions.Get( buildStep, SessionAim )
               : _buildActions.Get( buildStep, SessionAim );
    }
    #endregion
  }
}